# 代码功能说明：
# 实现了一个完整的两层神经网络类
# 包含两种梯度计算方式：
# numerical_gradient：数值梯度(慢但准确，用于验证)
# gradient：反向传播梯度(快速高效，用于实际训练)
# 主要方法：
# predict：前向传播计算输出
# loss：计算当前损失值
# accuracy：计算分类准确率
# 网络结构：
# 输入层 → (线性变换 + sigmoid) → 隐藏层 → (线性变换 + softmax) → 输出层
# 适用于多分类问题，输出层使用softmax激活函数
# 关键点：
# 使用字典存储网络参数，便于管理和访问
# 实现了完整的反向传播算法
# 数值梯度可用于验证反向传播实现的正确性
# 支持批量数据处理
# 权重初始化使用高斯分布，偏置初始化为0

# 指定文件编码为UTF-8
# coding: utf-8

"""
TwoLayerNet 类功能说明：
1. 实现了一个标准的两层神经网络(输入层-隐藏层-输出层)
2. 主要功能包括：
   - 网络参数初始化
   - 前向传播预测
   - 损失计算(交叉熵误差)
   - 准确率计算
   - 两种梯度计算方式(数值梯度和反向传播)
3. 激活函数使用sigmoid(隐藏层)和softmax(输出层)
4. 适用于分类任务
"""

# 导入系统模块和操作系统模块
import sys, os
# 添加父目录到系统路径，以便导入父目录中的模块
sys.path.append(os.pardir)

# 从common.functions导入激活函数和损失函数
from common.functions import *
# 从common.gradient导入数值梯度计算函数
from common.gradient import numerical_gradient

# 定义两层神经网络类
class TwoLayerNet:

    # 初始化函数
    def __init__(self, input_size, hidden_size, output_size, weight_init_std=0.01):
        # 初始化权重参数
        self.params = {}
        # 第一层权重(输入层到隐藏层)，使用高斯分布初始化
        self.params['W1'] = weight_init_std * np.random.randn(input_size, hidden_size)
        # 第一层偏置，初始化为0
        self.params['b1'] = np.zeros(hidden_size)
        # 第二层权重(隐藏层到输出层)，使用高斯分布初始化
        self.params['W2'] = weight_init_std * np.random.randn(hidden_size, output_size)
        # 第二层偏置，初始化为0
        self.params['b2'] = np.zeros(output_size)

    # 前向传播预测函数
    def predict(self, x):
        # 获取网络参数
        W1, W2 = self.params['W1'], self.params['W2']
        b1, b2 = self.params['b1'], self.params['b2']

        # 第一层计算
        a1 = np.dot(x, W1) + b1  # 线性变换
        z1 = sigmoid(a1)         # 激活函数

        # 第二层计算
        a2 = np.dot(z1, W2) + b2  # 线性变换
        y = softmax(a2)           # 输出层激活

        return y

    # 损失函数计算(x:输入数据, t:监督标签)
    def loss(self, x, t):
        y = self.predict(x)  # 获取预测结果
        return cross_entropy_error(y, t)  # 计算交叉熵误差

    # 准确率计算函数
    def accuracy(self, x, t):
        y = self.predict(x)
        y = np.argmax(y, axis=1)  # 获取预测类别
        t = np.argmax(t, axis=1)  # 获取真实类别

        # 计算准确率
        accuracy = np.sum(y == t) / float(x.shape[0])
        return accuracy

    # 数值梯度计算(x:输入数据, t:监督标签)
    def numerical_gradient(self, x, t):
        # 定义损失函数(用于梯度计算)
        loss_W = lambda W: self.loss(x, t)

        # 计算各参数的梯度
        grads = {}
        grads['W1'] = numerical_gradient(loss_W, self.params['W1'])
        grads['b1'] = numerical_gradient(loss_W, self.params['b1'])
        grads['W2'] = numerical_gradient(loss_W, self.params['W2'])
        grads['b2'] = numerical_gradient(loss_W, self.params['b2'])

        return grads

    # 反向传播梯度计算(更高效)
    def gradient(self, x, t):
        # 获取网络参数
        W1, W2 = self.params['W1'], self.params['W2']
        b1, b2 = self.params['b1'], self.params['b2']
        grads = {}  # 存储梯度

        batch_num = x.shape[0]  # 批大小

        # 前向传播
        a1 = np.dot(x, W1) + b1
        z1 = sigmoid(a1)
        a2 = np.dot(z1, W2) + b2
        y = softmax(a2)

        # 反向传播
        # 输出层梯度
        dy = (y - t) / batch_num  # softmax交叉熵的梯度
        grads['W2'] = np.dot(z1.T, dy)  # W2的梯度
        grads['b2'] = np.sum(dy, axis=0)  # b2的梯度

        # 隐藏层梯度
        da1 = np.dot(dy, W2.T)  # 反向传播到隐藏层
        dz1 = sigmoid_grad(a1) * da1  # 通过sigmoid的梯度
        grads['W1'] = np.dot(x.T, dz1)  # W1的梯度
        grads['b1'] = np.sum(dz1, axis=0)  # b1的梯度

        return grads
